package com.am.service;

import com.alibaba.fastjson.JSON;
import com.am.entity.Canvas;
import com.am.entity.OperateReq;
import com.am.entity.OperateResp;
import com.am.entity.Shape;
import com.am.util.Constants;
import com.am.util.DrawingPermission;
import com.am.util.MD5Code;
import com.am.util.ShapeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.websocket.EncodeException;
import javax.websocket.Session;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Slf4j
public class WebsocketHandler {

    //concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
    //虽然@Component默认是单例模式的，但springboot还是会为每个websocket连接初始化一个bean，所以可以用一个静态set保存起来。
    //  注：底下WebSocket是当前类名
//    private static CopyOnWriteArraySet<WebsocketController> webSockets = new CopyOnWriteArraySet<>();

    // 用来存在线连接用户信息
    public static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<String, Session>();

    //    画布名对应用户列表
    private static ConcurrentHashMap<String, List<Session>> canvasToUserList = new ConcurrentHashMap<>();

    //    房间名对应的画布列表
    private static ConcurrentHashMap<String, List<Canvas>> roomToCanvas = new ConcurrentHashMap<>();

    /**
     * 分发消息入口
     *
     * @return
     */
    public void FractionalTreatment(Session session, OperateReq req) {

        switch (req.getType()) {
            case Constants.OP_START: {//开始
                start(session, req);
                break;
            }
            case Constants.OP_JOIN: {//加入房间
                join(req, session);
                break;
            }
            case Constants.OP_CH_CANVAS: {//改变当前所在的画布,转移到另外一张画布
                change(req, session);
                break;
            }
            case Constants.OP_PAINT: {//绘画
                paint(req);
                break;
            }
            case Constants.OP_CHMOD: {//修改绘画权限
                chmod(req, session);
                return;
            }
            case Constants.OP_PREVIEW: {//预览 （鼠标东，没有确定画）
                preview(req);
                break;
            }
            case Constants.OP_ADD_CANVAS: {//添加画布
                addCanvas(req, session);
                break;
            }
            case Constants.OP_UNDO:  //用户撤销操作
            case Constants.OP_REDO: {//用户取消撤销
                undoAndRedo(req, session);
                return;
            }

            default: {

            }
        }
    }

    /**
     * 用户取消撤销
     * @param req
     * @param session
     */
    private void undoAndRedo(OperateReq req, Session session) {
        //找到对应的画布列表
        List<Canvas> canvasList = roomToCanvas.get(req.getRid());
        OperateResp resp = new OperateResp();
        BeanUtils.copyProperties(req, resp);//将req对象的属性值拷贝到resp对象相同字段上
        //遍历画布列表，找到对应的画布
        for (Canvas canvas : canvasList) {
            String canvasName = canvas.getName();
            if (canvasName.equals(req.getCid())) {//说明找到了画布
                //先判断该用户是否有对该画布操作的权限
                if (!DrawingPermission.drawing(canvas, req.getUid(), canvasName)) {//当前用户不能操作此画布
                    return;
                } else {//能操作
                    boolean boo;
                    //进行撤销操作
                    if (req.getType().equals(Constants.OP_UNDO)){
                        boo = ShapeUtil.undoShape(canvas, req);
                    }else {
                        boo = ShapeUtil.redoShape(canvas, req);
                    }
                    if (boo) {//撤销成功
                        resp.setCanvasList(roomToCanvas.get(req.getRid()));
                        sendMessages(resp); //通知画布中的所有人
                    } else {//撤销失败，用户没有在该画布上作画
                        resp.setCode(450);//撤销操作失败
                        sendMessage(resp,session);
                    }
                }
            }
        }
    }

    /**
     * 用户撤销上一步操作
     *
     * @param req
     * @param session
     */
    private void undo(OperateReq req, Session session) {
        //找到对应的画布列表
        List<Canvas> canvasList = roomToCanvas.get(req.getRid());
        OperateResp resp = new OperateResp();
        BeanUtils.copyProperties(req, resp);//将req对象的属性值拷贝到resp对象相同字段上
        //遍历画布列表，找到对应的画布
        for (Canvas canvas : canvasList) {
            String canvasName = canvas.getName();
            if (canvasName.equals(req.getCid())) {//说明找到了画布
                //先判断该用户是否有对该画布操作的权限
                if (!DrawingPermission.drawing(canvas, req.getUid(), canvasName)) {//当前用户不能操作此画布
                    return;
                } else {//能操作
                    //进行撤销操作
                    boolean boo = ShapeUtil.undoShape(canvas, req);
                    if (boo) {//撤销成功
                        resp.setCanvasList(roomToCanvas.get(req.getRid()));
                        sendMessages(resp); //通知画布中的所有人
                    } else {//撤销失败，用户没有在该画布上作画
                        resp.setCode(450);//撤销操作失败
                        sendMessage(resp,session);
                    }
                }
            }
        }
    }

    /**
     * 修改绘画权限
     */
    private void chmod(OperateReq req, Session session) {
        //先判断该用户是否是这个画布的房主
        if (!req.getUid().equals(req.getRid())) {
            return;
        }
        //找到这个画布，修改是否可以绘画
        List<Canvas> canvasList = roomToCanvas.get(req.getRid());
        for (Canvas canvas : canvasList) {
            if (canvas.getName().equals(req.getCid())) {
                canvas.setChmod(!canvas.getChmod());
            }
        }

        //将当前房间信息返回给用户
        OperateResp resp = new OperateResp();
        BeanUtils.copyProperties(req, resp); //将req对象的属性值拷贝到resp对象相同字段上
        resp.setType(req.getType());
        resp.setCanvasList(roomToCanvas.get(req.getRid()));
        sendMessages(resp);


    }

    /**
     * 用户在当前房间内添加画布
     *
     * @param req
     * @param session
     */
    private void addCanvas(OperateReq req, Session session) {
        //用户在当前房间中添加画布
        List<Canvas> canvasList = roomToCanvas.get(req.getRid());
        String endCanvasName = canvasList.get(canvasList.size() - 1).getName();
        String newCanvasName = req.getRid() + "_" + String.valueOf(Integer.parseInt(endCanvasName.substring(endCanvasName.indexOf("_") + 1)) + 1);
        canvasList.add(new Canvas(newCanvasName));

        //用户转移到新添加的画布中
        List<Session> sessionList = new ArrayList<>();
        sessionList.add(session);
        canvasToUserList.put(newCanvasName, sessionList);

        //将用户从原来的房间中出去
        canvasToUserList.get(req.getCid()).remove(session);

        //将当前房间信息返回给这个房间内的所有人（所有画布里面的人都要通知一遍）
        OperateResp resp = new OperateResp();
        BeanUtils.copyProperties(req, resp); //将req对象的属性值拷贝到resp对象相同字段上
        resp.setCanvasList(roomToCanvas.get(req.getRid()));
        //遍历发给当前房间内的所有画布
        for (Canvas canvas : roomToCanvas.get(req.getRid())) {
            resp.setCid(canvas.getName());
            sendMessages(resp);
        }
    }

    /**
     * 预览画面
     * 将上一次的操作删除
     * 为了形成动态效果
     *
     * @param req
     */
    private void preview(OperateReq req) {

        //找到对应的画布
        List<Canvas> canvasList = roomToCanvas.get(req.getRid());
        for (Canvas canvas : canvasList) {
            String canvasName = canvas.getName();
            if (canvas.getName().equals(req.getCid())) {
                /**
                 * 判断当前用户是否可以对当前画布进行操作
                 */
                if (!DrawingPermission.drawing(canvas,req.getUid(),canvasName)){//当前用户不能操作此画布
                    return;
                }

                //找到了该画布的画布列表
                List<Shape> shapes = canvas.getShapes();
                //找到了该画布的画布列表,将对应的操作添加进去
                canvas.getShapes().add(req.getShape());
            }
        }

        //封装响应对象参数
        OperateResp resp = new OperateResp();
        BeanUtils.copyProperties(req,resp);//将req对象的属性值拷贝到resp对象相同字段上
        resp.setType(req.getType());
        resp.setCid(req.getCid());
        resp.setCanvasList(roomToCanvas.get(req.getRid()));
        //向这个画布中的所有用户广播这条画画信息
        sendMessages(resp);

        for (Canvas canvas : canvasList) {
            if (canvas.getName().equals(req.getCid())) {
                //找到了该画布的画布列表
                List<Shape> shapes = canvas.getShapes();
                //找到了该画布的画布列表,将对应的操作删除
                shapes.remove(shapes.size() - 1);
            }
        }
    }
//    private void preview(OperateReq req) {
//
//        //找到对应的画布
//        List<Canvas> canvasList = roomToCanvas.get(req.getRid());
//        List<Canvas> copyCanvasList = new ArrayList<>();
//
//        for (int i = 0; i < canvasList.size(); i++) {
//            copyCanvasList.add(canvasList.get(i));
//        }
//        for (Canvas canvas : copyCanvasList) {
//            String canvasName = canvas.getName();
//            if (canvas.getName().equals(req.getCid())) {
//                /**
//                 * 判断当前用户是否可以对当前画布进行操作
//                 */
//                if (!DrawingPermission.drawing(canvas, req.getUid(), canvasName)) {//当前用户不能操作此画布
//                    return;
//                }
//                //找到了该画布的画布列表
//                List<Shape> shapes = canvas.getShapes();
//                //找到了该画布的画布列表,将对应的操作添加进去
//                canvas.getShapes().add(req.getShape());
//            }
//        }
//
//        //封装响应对象参数
//        OperateResp resp = new OperateResp();
//        BeanUtils.copyProperties(req, resp);//将req对象的属性值拷贝到resp对象相同字段上
//        resp.setType(req.getType());
//        resp.setCid(req.getCid());
//        resp.setCanvasList(copyCanvasList);
//        //向这个画布中的所有用户广播这条画画信息
//        sendMessages(resp);
//
//    }

    /**
     * 用户绘画
     *
     * @param req
     */
    private void paint(OperateReq req) {
        /**
         * 判断当前用户是否可以对当前画布进行操作
         */
        OperateResp resp = new OperateResp();

        //找到对应的画布，将用户的操作添加到画布的集合中
        List<Canvas> canvasList = roomToCanvas.get(req.getRid());
        for (Canvas canvas : canvasList) {
            String canvasName = canvas.getName();
            if (canvasName.equals(req.getCid())) {
                /**
                 * 判断当前用户是否可以对当前画布进行操作
                 */
                if (!DrawingPermission.drawing(canvas, req.getUid(), canvasName)) {//当前用户不能操作此画布
                    return;
                }
                //找到了该画布的操作列表,将对应的操作添加进去
                ShapeUtil.addShape(canvas, req);
//                canvas.getShapes().add(req.getShape());
            }
        }


        //封装响应对象参数
        resp.setType(req.getType());
        resp.setCid(req.getCid());
        resp.setCanvasList(roomToCanvas.get(req.getRid()));
        //向这个画布中的所有用户广播这条画画信息
        sendMessages(resp);

    }

    /**
     * 当前用户转移到其他画布上
     *
     * @param req
     * @param session
     */
    private void change(OperateReq req, Session session) {
        OperateResp resp = new OperateResp();
        //将当前用户添加到要去往的画布列表
        String toCid = req.getToCid();
        if (!canvasToUserList.containsKey(toCid)) {
            resp.setCode(404);
            sendMessage(resp, session);
        }

        //先将当前用户所在的session删除
        String cid = req.getCid();
        canvasToUserList.get(cid).remove(session);

        //将用户的session添加到新的画布 用户列表中
        canvasToUserList.get(toCid).add(session);

        //将当前房间信息返回给用户
        resp.setCid(req.getToCid());
        resp.setType(req.getType());
        String rid = resp.getCid().substring(0, resp.getCid().indexOf("_"));
        resp.setCanvasList(roomToCanvas.get(rid));
        resp.setType(req.getType());
        sendMessage(resp, session);
    }


    /**
     * 用户加入房间
     *
     * @param req
     * @param session
     */
    private void join(OperateReq req, Session session) {
        if (StringUtils.isEmpty(req.getUid())) {
            String uuid = MD5Code.getMD5FromTsp16();
            req.setUid(uuid);
        }
        OperateResp resp = new OperateResp();
        String cid = req.getCid();
        if (!canvasToUserList.containsKey(cid)) {
            resp.setCode(404);//房间没找到
            sendMessage(resp, session);
            return;
        }

        //将当当前用户放入画布 对应的  用户列表中
        canvasToUserList.get(cid).add(session);

        //将当前房间信息返回给前端
        String rid = req.getCid().substring(0, cid.indexOf("_"));
        resp.setCanvasList(roomToCanvas.get(rid));
        resp.setType(req.getType());
        resp.setUid(req.getUid());
        resp.setRid(rid);
        resp.setCid(req.getCid());
        sendMessage(resp, session);
    }

    /**
     * 开始的时候调用的方法
     *
     * @param session
     * @param req
     */
    private void start(Session session, OperateReq req) {
        if (StringUtils.isEmpty(req.getUid())) {
            String uuid = MD5Code.getMD5FromTsp16();
            req.setUid(uuid);
        }

        //房间名
        String roomName = req.getUid();  //房间号
        String canvasName = roomName + "_0"; //画布名
        Canvas canvas = new Canvas(canvasName);
        List<Canvas> canvasList = new ArrayList<>();
        canvasList.add(canvas);

        roomToCanvas.put(roomName, canvasList);

        //将用户当前的session方法画布中
        if (canvasToUserList.containsKey(canvasName)) {
            canvasToUserList.get(canvasName).add(session);
        } else {
            List<Session> sessionList = new ArrayList<>();
            sessionList.add(session);
            canvasToUserList.put(canvasName, sessionList);
        }

        //告诉前端它当前所在的画布名，房间名，用户id
        OperateResp resp = new OperateResp();
        resp.setCid(canvasName);
        resp.setUid(req.getUid());
        resp.setRid(req.getUid());
        resp.setCanvasList(roomToCanvas.get(roomName));
        resp.setType(req.getType());
        sendMessage(resp, session);
    }

    /**
     * 接收消息入口
     */
    public void sendMessages(OperateResp resp) {
        String cid = resp.getCid(); //
        List<Session> sessionList = canvasToUserList.get(cid);
        List<Session> indexList = new ArrayList<>();
        for (int i = 0; i < sessionList.size(); i++) {
            if (!sendMessage(resp, sessionList.get(i))) {
                indexList.add(sessionList.get(i)); //记录要删的session
            }
        }
        //遍历删除失效的session
        for (Session session : indexList) {
            sessionList.remove(session);
        }
    }

    /**
     * 发消息给单个用户
     *
     * @param resp
     * @param session
     * @throws EncodeException
     * @throws IOException
     */
    synchronized public Boolean sendMessage(OperateResp resp, Session session) {
        try {
            String message = JSON.toJSONString(resp);
            if (session.isOpen()) {
                session.getBasicRemote().sendText(message);
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 用户连接成功后调用的方法
     * 给用户一个uuid
     * 将用户加入到栈里面
     *
     * @param session
     * @return
     */
    public String onOpen(Session session) {
        String uuid = MD5Code.getMD5FromTsp();

        sessionPool.put(uuid, session);//将用户加入哈希表中  uuid作为它的用户名

        return uuid;

    }
}
